﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml.Linq;

namespace ForgeCraftServer
{
    public class XmlSettings
    {
        private Dictionary<string, XmlSetting> Values { get; set; }
        /// <summary>
        /// When true, all settings will be saved when modified or
        /// added.  (You may want to set this to false to load
        /// default values).
        /// </summary>
        public bool EnableSaving { get; set; }
        /// <summary>
        /// This event is called when any XML setting is changed
        /// </summary>
        public event EventHandler<SettingChangedEventArgs> OnSettingChanged;
        /// <summary>
        /// The location of the XML file that new keys
        /// should be stored in (when a key is added
        /// via XmlSettings["key"] without a file, it
        /// will be saved here.
        /// </summary>
        public static string DefaultFile { get; set; }

        /// <summary>
        /// Creates a new XmlSettings object with saving disabled.
        /// </summary>
        public XmlSettings()
        {
            Values = new Dictionary<string, XmlSetting>();
            EnableSaving = false;
        }

        /// <summary>
        /// Loads a single file into the dictionary.
        /// </summary>
        /// <param name="file">The path of the file to load.</param>
        public void LoadFile(string file)
        {
            try
            {
                Stream s = File.Open(file, FileMode.Open);
                XDocument d = XDocument.Load(s);
                s.Close();
                LoadRecursive(d.Root, file, string.Empty);
            }
            catch { }
        }

        /// <summary>
        /// Loads all xml files in a directory
        /// into the dictionary.
        /// </summary>
        /// <param name="SettingsDirectory">The directory to search through</param>
        /// <param name="Recursive">If true, this will include all subdirectories.  The default value is true.</param>
        /// <param name="Extension">The file extension to match.  The default value is "xml"</param>
        public void Load(string SettingsDirectory, bool Recursive = true, string Extension = "xml")
        {
            string[] SettingFiles = Directory.GetFiles(SettingsDirectory, "*." + Extension, Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
            foreach (string file in SettingFiles)
            {
                try
                {
                    Stream s = File.Open(file, FileMode.Open);
                    XDocument d = XDocument.Load(s);
                    s.Close();
                    LoadRecursive(d.Root, file, string.Empty);
                }
                catch { }
            }

            if (string.IsNullOrEmpty(DefaultFile))
                DefaultFile = Path.Combine(SettingsDirectory, "Settings.xml");
        }

        private void LoadRecursive(XElement root, string sourceFile, string path)
        {
            foreach (XElement e in root.Elements())
            {
                if (e.Elements().Count() != 0)
                    LoadRecursive(e, sourceFile, path + e.Name + ".");
                foreach (XAttribute a in e.Attributes())
                {
                    Values[(path + e.Name.LocalName.ToString() + "." +
                        a.Name.LocalName.ToString()).ToLower()] = new XmlSetting(sourceFile, a.Value, true);
                }
                if (Values.ContainsKey((path + e.Name.LocalName.ToString()).ToLower()))
                {
                    if (Values[(path + e.Name.LocalName.ToString()).ToLower()].Value != e.Value)
                    {
                        if (OnSettingChanged != null)
                            OnSettingChanged(this, new SettingChangedEventArgs((path + e.Name.LocalName.ToString()).ToLower(),
                                Values[(path + e.Name.LocalName.ToString()).ToLower()].Value, e.Value));
                    }
                }
                Values[(path + e.Name.LocalName.ToString()).ToLower()] = new XmlSetting(sourceFile, e.Value, false);
            }
        }

        /// <summary>
        /// Checks to see if the given
        /// key already exists.
        /// </summary>
        /// <param name="Key">The key to check</param>
        /// <returns>Returns true if the key is present in the dictionary.</returns>
        public bool ContainsKey(string Key)
        {
            return Values.ContainsKey(Key.ToLower());
        }

        /// <summary>
        /// Manipulates a given key.
        /// </summary>
        /// <param name="key">The key to fetch.</param>
        /// <returns>The string value of the selected key.</returns>
        public string this[string key]
        {
            get
            {
                if (!Values.ContainsKey(key.ToLower()))
                    return "";
                return Values[key.ToLower()].Value;
            }
            set
            {
                if (OnSettingChanged != null)
                    OnSettingChanged(this, new SettingChangedEventArgs(key, Values.ContainsKey(key.ToLower()) ? Values[key.ToLower()].Value : DefaultFile, value));
                if (Values.ContainsKey(key))
                    Values[key.ToLower()].Value = value;
                else
                    Values[key.ToLower()] = new XmlSetting(DefaultFile, value, false);

                if (string.IsNullOrEmpty(DefaultFile))
                    return;
                if (!EnableSaving)
                    return;

                XDocument d;

                if (File.Exists(Values[key.ToLower()].SourceFile))
                {
                    Stream s = File.Open(Values[key.ToLower()].SourceFile, FileMode.OpenOrCreate);
                    d = XDocument.Load(s, LoadOptions.PreserveWhitespace);
                    s.Close();
                }
                else
                {
                    Values[key.ToLower()].SourceFile = DefaultFile;
                    d = new XDocument();
                    d.Add(new XElement("partycraft"));
                }
                // Locate this property
                string[] parts = key.ToLower().Split('.');
                XElement currentElement = d.Root;
                for (int i = 0; i < parts.Length; i++)
                {
                    bool found = false;
                    if (parts.Length - 1 == i)
                    {
                        foreach (XAttribute a in currentElement.Attributes())
                        {
                            if (a.Name.LocalName.ToLower() == parts[i])
                            {
                                found = true;
                                break;
                            }
                        }
                    }
                    foreach (XElement e in currentElement.Elements())
                    {
                        if (e.Name.LocalName.ToLower() == parts[i])
                        {
                            currentElement = e;
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                    {
                        XElement el = new XElement(parts[i]);
                        currentElement.Add(el);
                        currentElement = el;
                    }
                }
                if (Values[key.ToLower()].IsAttribute)
                    currentElement.SetAttributeValue(parts[parts.Length - 1], Values[key.ToLower()].Value);
                else
                    currentElement.SetValue(Values[key.ToLower()].Value);

                d.Save(Values[key.ToLower()].SourceFile);
            }
        }

        #region Value Getters

        /// <summary>
        /// Gets the byte value of a given key.
        /// </summary>
        public byte GetByte(string Key)
        {
            byte i = 255;
            byte.TryParse(this[Key], out i);
            return i;
        }

        /// <summary>
        /// Gets the short value of a given key.
        /// </summary>
        public short GetShort(string Key)
        {
            short i = -1;
            short.TryParse(this[Key], out i);
            return i;
        }

        /// <summary>
        /// Gets the int value of a given key.
        /// </summary>
        public int GetInt(string Key)
        {
            int i = -1;
            int.TryParse(this[Key], out i);
            return i;
        }

        /// <summary>
        /// Gets the long value of a given key.
        /// </summary>
        public long GetLong(string Key)
        {
            long i = -1;
            long.TryParse(this[Key], out i);
            return i;
        }

        /// <summary>
        /// Gets the float value of a given key.
        /// </summary>
        public float GetFloat(string Key)
        {
            float i = -1;
            float.TryParse(this[Key], out i);
            return i;
        }

        /// <summary>
        /// Gets the double value of a given key.
        /// </summary>
        public double GetDouble(string Key)
        {
            double i = -1;
            double.TryParse(this[Key], out i);
            return i;
        }

        public bool GetBoolean(string Key)
        {
            bool i = false;
            bool.TryParse(this[Key], out i);
            return i;
        }

        #endregion
    }

    internal class XmlSetting
    {
        public string SourceFile { get; set; }
        public string Value { get; set; }
        public bool IsAttribute { get; set; }

        public XmlSetting(string SourceFile, string Value, bool IsAttribute)
        {
            this.SourceFile = SourceFile;
            this.Value = Value;
            this.IsAttribute = IsAttribute;
        }
    }

    /// <summary>
    /// Represents a setting that has changed.
    /// </summary>
    public class SettingChangedEventArgs : EventArgs
    {
        /// <summary>
        /// The key affected.
        /// </summary>
        public string Key { get; set; }
        /// <summary>
        /// The value of that key before the change.
        /// </summary>
        public string OldValue { get; set; }
        /// <summary>
        /// The key's new value.
        /// </summary>
        public string NewValue { get; set; }

        public SettingChangedEventArgs(string Key, string OldValue, string NewValue)
        {
            this.Key = Key;
            this.OldValue = OldValue;
            this.NewValue = NewValue;
        }
    }
}
